﻿using DotNetty.Codecs.Http;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using DotNetty.Transport.Libuv;
using System;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace DotNetty.Wraper
{
    class WebSocketServerBuilder :
        BaseGenericServerBuilder<IWebSocketServerBuilder, IWebSocketServer, IWebSocketConnection, string>,
        IWebSocketServerBuilder
    {
        public WebSocketServerBuilder(ServerOption _option)
            : base(_option)
        {
            if (option == null) throw new ArgumentNullException("option");
            if (string.IsNullOrWhiteSpace(_option.Path))
                _option.Path = "/";
        }

        public async override Task<IWebSocketServer> BuildAsync(Action<Transport.Channels.IChannelPipeline> OnPipelineAction = null, Func<IWebSocketServer, BaseSimpleChannelInboundHandler<object>> addChannelHandler = null)
        {
            //Environment.SetEnvironmentVariable("io.netty.allocator.numDirectArenas", "0");
            //Environment.SetEnvironmentVariable("io.netty.allocator.numHeapArenas", "0");
            System.Security.Cryptography.X509Certificates.X509Certificate2 tlsCertificate = null;
            if (!string.IsNullOrWhiteSpace(option.Certificate) && System.IO.File.Exists(option.Certificate))
            {
                tlsCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(option.Certificate, option.CertificatePassword);
            }
            var tcpServer = new WebSocketServer(tlsCertificate != null, option.Port, option.Path, _event);

            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
            }
            tcpServer.UseSsl = tlsCertificate != null;

            IEventLoopGroup bossGroup;
            IEventLoopGroup workGroup;
            var bootstrap = new ServerBootstrap();
            if (option.UseLibuv)
            {
                var dispatcher = new DispatcherEventLoopGroup();
                bossGroup = dispatcher;
                workGroup = new WorkerEventLoopGroup(dispatcher);
                bootstrap.Group(bossGroup, workGroup);

                bootstrap.Channel<TcpServerChannel>();
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
                    || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                {
                    bootstrap
                        .Option(ChannelOption.SoReuseport, true)
                        .ChildOption(ChannelOption.SoReuseaddr, true);
                }
            }
            else
            {
                bossGroup = new MultithreadEventLoopGroup(option.EventLoopCount);
                workGroup = new MultithreadEventLoopGroup();
                bootstrap.Group(bossGroup, workGroup);

                bootstrap.Channel<TcpServerSocketChannel>();
            }

            //IChannel bootstrapChannel = await 
            bootstrap
            .Option(ChannelOption.SoBacklog, option.SoBacklog)
            .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
            {
                IChannelPipeline pipeline = channel.Pipeline;
                if (tcpServer.UseSsl)
                {
                    pipeline.AddLast(Handlers.Tls.TlsHandler.Server(tlsCertificate));
                }
                pipeline.AddLast(new HttpServerCodec());
                pipeline.AddLast(new HttpObjectAggregator(65536));
                OnPipelineAction?.Invoke(pipeline);

                if (addChannelHandler != null)
                    pipeline.AddLast(addChannelHandler(tcpServer));
                else
                    pipeline.AddLast(new CommonChannelHandler(tcpServer));
            }));//.BindAsync(option.Port);
            IChannel _channel;
            if (option.BindType == AddressBindType.Any)
            {
                _channel = await bootstrap.BindAsync(option.Port);
            }
            else if (option.BindType == AddressBindType.InternalAddress)
            {
                var localPoint = IPUtility.GetLocalIntranetIP();
                if (localPoint == null)
                {
                    //this._logger.LogWarning("there isn't an avaliable internal ip address,the service will be hosted at loopback address.");
                    _channel = await bootstrap.BindAsync(System.Net.IPAddress.Loopback, option.Port);
                }
                else
                {
                    //this._logger.LogInformation("TcpServerHost bind at {0}",localPoint);
                    _channel = await bootstrap.BindAsync(localPoint, this.option.Port);

                }
            }
            else if (option.BindType == AddressBindType.Loopback)
            {
                _channel = await bootstrap.BindAsync(System.Net.IPAddress.Loopback, option.Port);
            }
            else
            {
                _channel = await bootstrap.BindAsync(System.Net.IPAddress.Parse(option.SpecialAddress), option.Port);
            }

            _event.OnServerStarted?.Invoke(tcpServer);

            tcpServer.SetChannel(_channel);
            tcpServer.AfterClose = () =>
            {
                Task.WhenAll(
                   bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
                   workGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1))).Wait();
            };

            return await Task.FromResult(tcpServer);
        }
    }
}